ONNX Runtime en el edge: inferencia portable y rápida

Placa electrónica con chip central iluminado representando inferencia ML en hardware

ONNX Runtime es el runtime de inferencia multiplataforma impulsado por Microsoft que convierte ONNX (Open Neural Network Exchange) de especificación a herramienta práctica. Tu modelo PyTorch o TensorFlow, exportado a ONNX, se ejecuta casi igual en servidor Linux, móvil Android, iPhone, browser y edge device. Este artículo cubre cómo usarlo bien, cuándo es la elección correcta y dónde queda corto.

Por qué ONNX importa

El problema que resuelve: fragmentación de runtimes. Entrenas en PyTorch, sirves en TensorFlow Serving. Quieres también en móvil: Core ML en iOS, TensorFlow Lite en Android. Browser: TensorFlow.js. Cada paso es una conversión distinta con su set de bugs.

ONNX ofrece un formato intermedio abierto. ONNX Runtime lo ejecuta. Un solo modelo → muchas plataformas.

Export desde PyTorch

import torch
import torch.onnx

model = MyModel()
model.inference_mode()

dummy_input = torch.randn(1, 3, 224, 224)

torch.onnx.export(
    model,
    dummy_input,
    "model.onnx",
    input_names=["input"],
    output_names=["output"],
    dynamic_axes={
        "input": {0: "batch_size"},
        "output": {0: "batch_size"}
    },
    opset_version=17
)

Puntos clave:

  • opset_version: versión de operators ONNX. Más nuevo = más ops soportados, pero runtime debe coincidir.
  • dynamic_axes: ejes variables (batch size típicamente). Sin esto, el export es static-shape.
  • Verificar con onnx.checker.check_model(model).

Inferencia básica

import onnxruntime as ort
import numpy as np

session = ort.InferenceSession("model.onnx")

input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
outputs = session.run(None, {"input": input_data})

Simple, sin dependencias pesadas. La memoria de ONNX Runtime es ~15MB base + pesos del modelo.

Execution Providers: el secreto del rendimiento

ONNX Runtime soporta Execution Providers (EPs) que aceleran según hardware:

  • CPU: por defecto, optimizado.
  • CUDA: GPUs NVIDIA.
  • TensorRT: mejor aún en NVIDIA, con conversión adicional.
  • OpenVINO: Intel CPUs, GPUs integradas, VPUs.
  • CoreML: Apple Silicon.
  • DirectML: Windows GPUs (incluidas AMD, Intel).
  • ROCm: AMD GPUs Linux.
  • WebGPU / WebNN: browser.
  • NNAPI: Android.
  • QNN: Qualcomm (Snapdragon).
  • MIGraphX: AMD data center.

Ejemplo eligiendo EP:

session = ort.InferenceSession(
    "model.onnx",
    providers=[
        "CUDAExecutionProvider",
        "CPUExecutionProvider"
    ]
)

El runtime elige el primer disponible. Fallback seguro si el hardware no está.

Casos donde ONNX brilla

  • Apps móviles con modelos ML: el mismo export corre en iOS y Android sin trabajo extra.
  • Edge devices heterogéneos: Jetson, Raspberry Pi, industrial gateways.
  • Browser inference via onnxruntime-web: modelos corren en tab sin servidor.
  • Transición de frameworks: exportar desde PyTorch, servir desde cualquier stack.
  • Compliance / control: empresas que quieren evitar dependencia de proveedor ML cloud.

Casos donde ONNX queda corto

  • Modelos LLM grandes (>7B params): ONNX Runtime soporta, pero vLLM o TensorRT-LLM son más eficientes.
  • Custom ops no estándar: si tu modelo usa kernels PyTorch muy custom, pueden no exportar.
  • Training: ONNX Runtime Training existe pero es nicho; PyTorch domina.
  • Modelos muy nuevos: los operators cutting-edge pueden no estar en ONNX opset.

Optimizaciones

ONNX Runtime incluye optimizaciones automáticas al cargar:

  • Graph optimization: fusion de ops, constant folding.
  • Kernel fusion específica por EP.
  • Quantization opcional — INT8, INT4 — con degradación mínima de calidad.

Quantization en un line:

from onnxruntime.quantization import quantize_dynamic

quantize_dynamic("model.onnx", "model_int8.onnx")

Reducción típica: 4x tamaño, 2-4x más rápido, <1% pérdida de calidad.

ONNX Runtime Web

Una feature infravalorada: onnxruntime-web ejecuta modelos ONNX directamente en navegador con WebGPU o WebAssembly.

import * as ort from 'onnxruntime-web';

const session = await ort.InferenceSession.create(
  './model.onnx',
  { executionProviders: ['webgpu', 'wasm'] }
);

const feeds = { input: new ort.Tensor('float32', data, [1, 3, 224, 224]) };
const results = await session.run(feeds);

Usos: image classification, object detection, whisper para transcripción — todo en cliente sin servidor.

Mobile: ONNX Runtime Mobile

Versión optimizada para móvil con binarios pequeños:

  • Android: .aar integrable en proyectos Gradle.
  • iOS: framework Swift/Objective-C.
  • React Native: bindings existentes.
  • Flutter: plugins comunitarios.

Para modelos de 20-100MB en apps móviles, es la opción más sencilla.

Alternativas a considerar

  • PyTorch JIT / LibTorch: para stay-in-PyTorch deployment.
  • TensorFlow Lite: para ecosistema TF, bueno en móvil.
  • TensorRT (NVIDIA): ceiling de rendimiento en GPUs NVIDIA, pero lock-in.
  • CoreML (Apple): óptimo en Apple Silicon exclusivamente.
  • OpenVINO (Intel): excelente en hardware Intel.

ONNX Runtime es el “universal” trade-off: menor top-end que especializados, pero portable.

Workflow típico de desarrollo

Patrón que funciona:

  1. Entrenar en PyTorch/TF con GPU.
  2. Export a ONNX con torch.onnx.export.
  3. Validar que la salida coincide con el modelo original.
  4. Optimizar: onnxoptimizer + quantization si aplica.
  5. Benchmark en cada target (server, móvil, browser).
  6. Desplegar con ONNX Runtime en cada plataforma.

Para cada target, ~1 día de adaptación vs semanas de rework con otros stacks.

Validación post-export

Crítico: verificar que el modelo exportado produce los mismos outputs que el original:

import onnxruntime as ort
import numpy as np

torch_out = model(test_input).detach().numpy()

session = ort.InferenceSession("model.onnx")
onnx_out = session.run(None, {"input": test_input.numpy()})[0]

assert np.allclose(torch_out, onnx_out, rtol=1e-3, atol=1e-5)

Divergencias comunes:

  • Ops custom no soportadas completamente.
  • Precision differences (float32 vs float16).
  • Batch normalization con sutilezas de training vs inference mode.

Modelos preconvertidos: ONNX Zoo

El ONNX Model Zoo tiene docenas de modelos ya convertidos y verificados: YOLOv8, BERT, ResNet, SSD, MobileNet, y más. Si tu caso encaja con uno, te ahorras el export.

Limitaciones de performance

En benchmarks:

  • CPU: ONNX Runtime ≥ TensorFlow, < PyTorch JIT en algunos casos.
  • GPU NVIDIA: ONNX Runtime CUDA ~95% de TensorRT, sin la complejidad.
  • GPU Apple: CoreML > ONNX Runtime para modelos optimizados a Apple.
  • Mobile: competitivo con TFLite en la mayoría.

Si necesitas el absoluto top de rendimiento en una plataforma específica, el runtime nativo de esa plataforma gana. ONNX Runtime gana en portabilidad y simplicidad.

Operación en producción

Checklist:

  • Model versioning: hash + metadata en tu registro.
  • Monitoring: latency, throughput por session.
  • Memory management: sessions pueden acumular si no liberan.
  • Warmup: primera inferencia más lenta por kernel compilation.
  • Fallback EP: siempre CPUExecutionProvider al final.

Conclusión

ONNX Runtime es una herramienta potente para equipos que sirven ML en múltiples plataformas o que quieren evitar lock-in con un runtime específico. Su portabilidad es su gran ventaja; su límite es que no es el absoluto top en ninguna plataforma individual. Para apps móviles, edge heterogéneo, inferencia en browser, y transición entre frameworks, es casi siempre la elección sensata. Para workloads de solo-servidor-NVIDIA-grande, puede haber alternativas ligeramente más óptimas. Su madurez y el ecosistema Microsoft detrás le dan continuidad asegurada.

Síguenos en jacar.es para más sobre ML en producción, edge computing y arquitecturas de inferencia.

Entradas relacionadas